/**
 * Copyright ®2015 sinosoft Co. Ltd. All rights reserved.
 */
package com.gavinwind.inspay.common.util;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Vector;

/**
 * Sftp调用工具类
 * 
 * @date 2010-11-20
 * @since JDK 1.5
 * @author dingkui
 * @version 1.0
 * 
 */
public class SftpUtil {

    /**
     * jcraft的ChannelSftp类实例，信息传输通道
     */
    private ChannelSftp channelSftp;

    /**
     * jcraft的session类实例，用来持久化连接
     */
    private Session session;

    /**
     * 当前操作路径
     */
    private String currentPath;

    /**
     * 当前目录下文件列表
     */
    @SuppressWarnings("rawtypes")
    private Vector currentFiles;

    /**
     * 取得当前的ChannelSftp实例
     * 
     * @return 当前的ChannelSftp实例
     */
    public ChannelSftp getChannelSftp() {
        return channelSftp;
    }

    /**
     * 根据指定config相关信息，连接远程sftp服务器
     * 
     * @param cFtpConfig
     *            ftp服务配置信息类
     */
    public void connectServer(FtpConfig cFtpConfig) {
        String tServer = cFtpConfig.getServer();
        int tPort = cFtpConfig.getPort();
        String tUsername = cFtpConfig.getUsername();
        String tPassword = cFtpConfig.getPassword();
        String tLocation = "/";
        if (null != cFtpConfig.getLocation() && !"".equals(cFtpConfig.getLocation())) {
            tLocation = cFtpConfig.getLocation();
        }
        String tEncode = cFtpConfig.getEncode();
        connectServer(tServer, tPort, tUsername, tPassword, tLocation, tEncode);
    }

    /**
     * 链接远程ftp服务器
     * 
     * @param cServer
     *            服务器地址
     * @param cPort
     *            服务器端口
     * @param cUser
     *            用户名
     * @param cPassword
     *            密码
     * @param cPath
     *            登陆后的默认路径
     * @param cEncode
     *            服务器文件系统编码
     */
    public void connectServer(String cServer, int cPort, String cUser, String cPassword, String cPath, String cEncode) {
        String tErrorMsg = "";
        try {
            JSch tJsch = new JSch();
            session = tJsch.getSession(cUser, cServer, cPort);
            session.setPassword(cPassword);
            // session.setUserInfo(new SftpUserInfo(ftpPassword));
            Properties tSshConfig = new Properties();
            tSshConfig.put("StrictHostKeyChecking", "no");
            session.setConfig(tSshConfig);
            session.connect();
            channelSftp = (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();
            channelSftp.setFilenameEncoding(cEncode);

        } catch (SftpException e) {
            tErrorMsg = "无法使用SFTP传输文件!";
            e.printStackTrace();
            throw new RuntimeException(tErrorMsg);
        } catch (JSchException e) {
            tErrorMsg = "没有权限与SFTP服务器连接!";
            e.printStackTrace();
            throw new RuntimeException(tErrorMsg);
        }
    }

    /**
     * 内部方法，关闭OutputStream
     * 
     * @param cOutputStream
     *            希望关闭的OutputStream
     */
    private void closeOutputStream(OutputStream cOutputStream) {
        try {
            if (null != cOutputStream) {
                cOutputStream.close();
            }
        } catch (IOException e) {
            throw new RuntimeException("关闭OutputStream时出现异常：" + e.getMessage());
        }
    }

    /**
     * 内部方法，关闭InputStream
     * 
     * @param cInputStream
     *            希望关闭的InputStream
     */
    private void closeInputStream(InputStream cInputStream) {
        try {
            if (null != cInputStream) {
                cInputStream.close();
            }
        } catch (IOException e) {
            throw new RuntimeException("关闭OutputStream时出现异常：" + e.getMessage());
        }
    }

    /**
     * 内部方法，取得当前操作目录下的全部文件列表
     */
    @SuppressWarnings("rawtypes")
    private void getCurrentFileList() {
        try {
            Vector tVector = channelSftp.ls(currentPath);
            this.currentFiles = tVector;
        } catch (SftpException e) {
            e.printStackTrace();
        }
    }

    /**
     * 内部方法，获得操作路径
     * 
     * @param cPath
     *      参数
     * @return String
     */
    private String getOperationPath(String cPath) {
        String tOperactePath;
        if ('/' == cPath.charAt(0)) {
            tOperactePath = cPath;
        } else {
            tOperactePath = currentPath + cPath;
        }
        if (tOperactePath.lastIndexOf("/") != tOperactePath.length() - 1) {
            tOperactePath = tOperactePath + "/";
        }
        return tOperactePath;
    }

    /**
     * 内部方法，获得操作路径
     * 
     * @param path
     *      参数
     * @return String
     */
    private String getFileOperationPath(String cPath) {
        String tOperactePath;
        if ('/' == cPath.charAt(0)) {
            tOperactePath = cPath;
        } else {
            tOperactePath = currentPath + "/" + cPath;
        }
        return tOperactePath;
    }

    /**
     * 内部方法，验证路径和sftp连接
     * 
     * @param path
     *            路径
     * @return 验证结果
     */
    private boolean dirValidation(String cPath) {
        boolean tResult = true;
        if (channelSftp == null || !channelSftp.isConnected()) {
            tResult = false;
            throw new RuntimeException("操作出现异常：channelSftp连接未创建");
        }
        if (null != cPath && !"".equals(cPath)) {
            tResult = false;
            throw new RuntimeException("操作出现异常：指定的path不能为空");
        }
        return tResult;
    }

    /**
     * 内部方法，判断一个字符串，是文件路径
     * 
     * @param cPath
     *            路径字符串
     * @return 判断结果，是返回ture，否则false
     */
    private boolean isFilePath(String cPath) {
        boolean tResult = false;
        if (null != cPath && !"".equals(cPath) && cPath.lastIndexOf("/") < cPath.length()) {
            tResult = true;
        }
        return tResult;
    }

    /**
     * 变换当前目录
     * 
     * @param cPath
     *            希望进入的目录
     * @return 成功为true，失败返回false
     */
    public boolean changeDir(String cPath) {
        boolean tResult = false;
        if (dirValidation(cPath)) {
            String tTestPath = getOperationPath(cPath);
            try {
                channelSftp.cd(tTestPath);
                this.currentPath = tTestPath;
                tResult = true;
            } catch (SftpException e) {
                throw new RuntimeException("变换目录'" + cPath + "'时发生错误：" + e.getMessage());
            }
        }
        return tResult;
    }

    /**
     * 创建目录
     * 
     * @param cRemotePath
     *            远程目录
     * @return 操作结果，成功为true，失败为false
     */
    public boolean makeDir(String cRemotePath) {
        boolean tResult = false;
        if (dirValidation(cRemotePath)) {
            String tTestPath = getOperationPath(cRemotePath);
            try {
                channelSftp.mkdir(tTestPath);
                tResult = true;
            } catch (SftpException e) {
                throw new RuntimeException("创建目录'" + cRemotePath + "'时发生错误：" + e.getMessage());
            }
        }
        return tResult;
    }

    /**
     * 删除远程服务器上的目录（仅可删除非空目录）
     * 
     * @param cRemotePath
     *            远程目录
     * @return 操作结果，成功为true，失败为false
     */
    public boolean removeDir(String cRemotePath) {
        boolean tResult = false;
        if (dirValidation(cRemotePath)) {
            String tTestPath = getOperationPath(cRemotePath);
            try {
                channelSftp.rmdir(tTestPath);
                tResult = true;
            } catch (SftpException e) {
                throw new RuntimeException("删除目录'" + cRemotePath + "'时发生错误：" + e.getMessage());
            }
        }
        return tResult;
    }

    /**
     * 内部方法，取得一个文件他所属的目录的路径
     * 
     * @param path
     *            文件路径
     * @return 判断结果，是返回ture，否则false
     */
    private String getFileDir(String cPath) {
        String tResult = "";
        if (cPath.lastIndexOf("/") >= 0) {
            tResult = cPath.substring(0, cPath.lastIndexOf("/"));
        }
        return tResult;
    }

    /**
     * 判断文件是否存在
     * 
     * @param cRemoteFilePath
     *            文件路径
     * @return 文件存在，则返回true，否则返回false
     */
    @SuppressWarnings("rawtypes")
    public boolean existFile(String cRemoteFilePath) {
        boolean tResult = false;
        if (dirValidation(cRemoteFilePath)) {
            if (!this.isFilePath(cRemoteFilePath)) {
                throw new RuntimeException("这不是一个文件路径：" + cRemoteFilePath);
            }
            String tPathDir = this.getFileDir(cRemoteFilePath);

            String tRealPathDir = this.getOperationPath(tPathDir);
            String tFileName = "";
            try {
                Vector tVector = channelSftp.ls(tRealPathDir);
                if (null != tVector && tVector.size() > 0) {
                    for (int i = 0; i < tVector.size(); ++i) {
                        LsEntry tEntry = (LsEntry) tVector.get(i);
                        if (tEntry.getFilename().equals(tFileName)) {
                            tResult = true;
                            break;
                        }
                    }
                }
            } catch (SftpException e1) {
                e1.printStackTrace();
            }
        }
        return tResult;
    }

    /**
     * 取得当前操作路径下的文件名列表(含文件夹)
     * 
     * @return 文件名列表
     */
    public List<String> getFileList() {
        List<String> tResult = null;
        if (null != currentFiles && currentFiles.size() > 0) {
            tResult = new ArrayList<String>();
            for (int i = 0; i < currentFiles.size(); ++i) {
                tResult.add(((LsEntry) currentFiles.get(i)).getFilename());
            }
        }
        return tResult;
    }

    /**
     * 从SFTP服务器下载文件至本地
     * 
     * @param cRemoteFilePath
     *            远程文件路径
     * @param cLocalFilePath
     *            本地文件路径
     * @return boolean
     */
    public boolean downloadFile(String cRemoteFilePath, String cLocalFilePath) {
        if (dirValidation(cRemoteFilePath)) {
            if (!existFile(cRemoteFilePath)) {
                throw new RuntimeException("下载文件" + cRemoteFilePath + "时发生异常，远程文件并不存在");
            }
            OutputStream tOutputStream = null;
            try {
                String tRealPath = getFileOperationPath(cRemoteFilePath);
                File tFile = new File(cLocalFilePath);
                FileOutputStream tFos = new FileOutputStream(tFile);
                // 从服务器下载文件到本地
                channelSftp.get(tRealPath, tFos);
                this.getCurrentFileList();
                return true;
            } catch (IOException e) {
                throw new RuntimeException("下载文件：" + cRemoteFilePath + "时发生文件读写错误：" + e.getMessage());
            } catch (SftpException e) {
                throw new RuntimeException("下载文件：" + cRemoteFilePath + "时发生Sftp错误：" + e.getMessage());
            } finally {
                this.closeOutputStream(tOutputStream);
            }
        } else {
            return false;
        }
    }

    /**
     * 下载文件，并写入HttpServletResponse（浏览器方式）
     * @param cRemoteFilePath 远程文件路径
     * @param cResponse HttpServletResponse
     * @param cFileName 通过浏览器，下载时看到的文件名
     * @return 操作结果，成功返回true，失败返回false
     */
    public boolean downloadFile(String cRemoteFilePath, HttpServletResponse cResponse, String cFileName) {
        if (dirValidation(cRemoteFilePath)) {
            if (!existFile(cRemoteFilePath)) {
                throw new RuntimeException("下载文件" + cRemoteFilePath + "时发生异常，远程文件并不存在");
            }
            OutputStream tOutputStream = null;
            //String fileName = cRemoteFilePath.substring(cRemoteFilePath.lastIndexOf(File.separator) + 1);
            try {
                cResponse.setHeader("Content-disposition", "attachment;filename=" + new String(cFileName.getBytes("GBK"), "ISO8859-1"));
                tOutputStream = cResponse.getOutputStream();
                // 从服务器下载到本地
                channelSftp.get(cRemoteFilePath, tOutputStream);
                return true;
            } catch (IOException e) {
                throw new RuntimeException("下载文件：" + cRemoteFilePath + "时发生文件读写错误：" + e.getMessage());
            } catch (SftpException e) {
                throw new RuntimeException("下载文件：" + cRemoteFilePath + "时发生Sftp错误：" + e.getMessage());
            } finally {
                this.closeOutputStream(tOutputStream);
            }
        } else {
            return false;
        }
    }

    /**
     * 删除服务器上的文件
     * 
     * @param cRemoteFilePath
     *            远程文件的完整路径
     * @return 操作结果(boolean型)
     */
    public boolean deleteFile(String cRemoteFilePath) {
        if (dirValidation(cRemoteFilePath)) {
            if (!existFile(cRemoteFilePath)) {
                throw new RuntimeException("删除文件" + cRemoteFilePath + "时发生异常，远程文件并不存在");
            }
            try {
                String tRrealPath = getFileOperationPath(cRemoteFilePath);
                channelSftp.rm(tRrealPath);
                return true;
            } catch (SftpException e) {
                throw new RuntimeException("删除文件：" + cRemoteFilePath + "时发生错误：" + e.getMessage());
            }
        } else {
            return false;
        }
    }

    /**
     * 上传文件
     * 
     * @return -1 文件不存在 -2 文件内容为空 >0 成功上传，返回文件的大小
     * @param cRemoteFilePath
     *            远程文件名
     * @param cLocalFilepath
     *            本地文件名
     */
    @SuppressWarnings("finally")
    public long upload(String cRemoteFilePath, String cLocalFilepath) {
        if (channelSftp == null || !channelSftp.isConnected()) {
            throw new RuntimeException("上传文件" + cLocalFilepath + "时发生异常，channelSftp连接未创建");
        }
        InputStream tIs = null;
        long tResult = -1;
        try {
            File tFileIn = new File(cLocalFilepath);

            if (tFileIn.exists()) {
                tIs = new FileInputStream(tFileIn);
                tResult = tFileIn.length();
                // 从本地上传到服务器
                channelSftp.put(tIs, cRemoteFilePath);
            } else {
                tResult = -1;
            }
        } catch (IOException e) {
            tResult = -1;
            throw new RuntimeException(e.getMessage() + ": 上传文件时发生错误！");
        } catch (SftpException e) {
            throw new RuntimeException(e.getMessage() + ": 上传文件时发生错误！");
        } finally {
            closeInputStream(tIs);
            return tResult;
        }
    }

    /**
     * 上传文件
     * @param cFilename 文面名
     * @return 操作结果
     */
    public long upload(String cFilename) {
        String tNewname = "";
        if (cFilename.lastIndexOf("/") > -1) {
            tNewname = cFilename.substring(cFilename.lastIndexOf("/") + 1, cFilename.length());
        } else {
            tNewname = cFilename;
        }
        return upload(tNewname, cFilename);
    }

    /**
     * 关闭连接
     */
    public void closeConnection() {
        if (channelSftp != null) {
            if (channelSftp.isConnected()) {
                channelSftp.disconnect();
                session.disconnect();
            }
        }
    }

}
